import { _decorator, Component, Node, SpriteAtlas, Prefab, AudioClip, Label,
    sys, Button, view, UITransform, resources, UIOpacity, Sprite, instantiate,
    tween, Vec3, Tween, Size, log, AudioSource, JsonAsset, Layout, Layers,
} from 'cc';
import { LevelItem } from './levelItem';
const { ccclass, property } = _decorator;

let boxType = {
    NONE : 0,   //无效位置
    WALL : 1,   //墙面
    LAND : 2,   //地面
    BODY : 3,   //物体
    BOX : 4,    //箱子
    ENDBOX : 5, //合体后的箱子
    HERO : 6    //人物
}
let sound = {
    BUTTON : "Texture/sound/button",      //按钮点击音效
    GAMEWIN : "Texture/sound/gamewin",    //过关音效
    MOVE : "Texture/sound/move",          //人物移动音效
    PUSHBOX : "Texture/sound/pushbox",    //推箱子音效
    WRONG : "Texture/sound/wrong",        //错误音效
}
@ccclass('GameLayer')
export class GameLayer extends Component {
    @property(Node)
    bg !:Node;
    @property(Node)
    menuLayer !:Node;
    @property(Node)
    levelLayer !:Node;
    @property(Node)
    gameLayer !:Node;
    @property(Node)
    gameControlLayer !:Node;
    @property(Node)
    gameOverLayer !:Node;
    @property(Node)
    startBtn !:Node;
    @property(Node)
    titleImg !:Node;
    @property(Node)
    iconImg !:Node;
    @property(Node)
    loadingTxt !:Node;
    @property(Node)
    levelScroll !:Node;
    @property(Node)
    levelContent !:Node;
    @property(Node)
    levelTxt !:Node;
    @property(Node)
    curNum !:Node;
    @property(Node)
    bestNum !:Node;
    @property(SpriteAtlas)
    itemImgAtlas !:SpriteAtlas;
    @property(SpriteAtlas)
    levelImgAtlas !:SpriteAtlas;
    @property(Prefab)
    levelItemPrefab !:Prefab;

    curLayer : number = 0;
    boxWidth : number = 0;
    boxHeight : number = 0;
    allWidth : number = 0;
    allHeight : number = 0;
    allRow : number = 0;
    allCol : number = 0;
    allLevelCount : number = 0;
    allLevelConfig : any;
    tabLevel : any[] = [];
    curLevel : number = 0;
    heroRow : number = 0;
    heroCol : number = 0;
    boxW : number = 0;
    boxH : number = 0;
    landArrays : any[] = [];
    palace : any[] = [];
    finishBoxCount : number = 0;
    _end : any = {};
    _start : any = {};
    minPath : number = 0;
    bestMap : any[] = [];
    curStepNum : number = 0;

    onLoad () {
        this.curLayer = 0;  //0-主界面，1-关卡界面，2-游戏界面
        this.clearGameData();
        this.initData();
        this.menuLayer.active = true;
        this.levelLayer.active = false;
        this.gameLayer.active = false;
        this.gameOverLayer.active = false;
        this.loadingTxt.active = true;
        this.startBtn.getComponent(Button)!.interactable = false;
        this.fitNode(this.bg);
        this.menuLayerAni();
    }

    fitNode (obj: any) {
        let canvasSize = view.getCanvasSize();
        let canvasScale = canvasSize.width / canvasSize.height;
        let designScale = 720 / 1280;
        const bgSize = this.bg.getComponent(UITransform)!.contentSize.clone();
        bgSize.height = 1280 * (designScale / canvasScale);;
        this.bg.getComponent(UITransform)?.setContentSize(bgSize);
    }

    initData () {
        this.boxWidth = 90;
        this.boxHeight = 90;
        this.allWidth = 720;
        this.allHeight = 1280;
        this.allRow = 8;
        this.allCol = 8;
        this.allLevelCount = 0;
        this.allLevelConfig = {};
        resources.load('levelConfig', JsonAsset, (err: any, object: any) => {
           if (err) {
               console.log(err);
               return;
           }
           this.allLevelConfig = object.json.level;
           this.allLevelCount = object.json.levelCount;
           this.loadingTxt.active = false;
           this.startBtn.getComponent(Button)!.interactable = true;
           this.createLavelItem();
        });
        this.tabLevel = [];
    }

    createLavelItem () {
        let callfunc = (level: any) => {            
           this.selectLevelCallBack(level);
        };
        for(let i = 0; i < this.allLevelCount; i++){
           let node = instantiate(this.levelItemPrefab);
           node.parent = this.levelScroll;
           let levelItem = node.getComponent(LevelItem)!;
           levelItem.levelFunc(callfunc);
           this.tabLevel.push(levelItem);
        }

        let levelSize = this.levelContent.getComponent(UITransform)!.contentSize.clone();
        levelSize.height = Math.ceil(this.allLevelCount / 5) * 135 + 20;
        this.levelContent.getComponent(UITransform)!.setContentSize(levelSize);
        // 刷新layout
        this.levelScroll.getComponent(Layout)!.updateLayout();
    }

    updateLevelInfo () {
        let finishLevel = parseInt(sys.localStorage.getItem("finishLevel") || 0);  //已完成关卡
        for(let i = 1; i <= this.allLevelCount; i++){
           if(i <= finishLevel){
               let data = parseInt(sys.localStorage.getItem("levelStar" + i) || 0);
               this.tabLevel[i - 1].showStar(true, data, this.levelImgAtlas, i);
           }
           else if(i == (finishLevel + 1)){
               this.tabLevel[i - 1].showStar(true, 0, this.levelImgAtlas, i);
           }
           else{  
               this.tabLevel[i - 1].showStar(false, 0, this.levelImgAtlas, i);
           }
        }
    }

    menuLayerAni () {
        this.startBtn.setScale(1.0, 1.0, 1.0);
        tween(this.startBtn).repeatForever(
            tween(this.startBtn)
                .to(0.6, { scale: new Vec3(1.5, 1.5, 1.5) })
                .to(0.6, { scale: new Vec3(1.0, 1.0, 1.0) })
        ).start();
    }

    startBtnCallBack (event: any, customEventData: any) {
        if(this.curLayer == 1){
           return;
        }
        this.curLayer = 1;
        this.playSound(sound.BUTTON);
        
        tween(this.menuLayer)
            .to(0.1, {}, {easing: 'fade'})
            .call(()=>{
                Tween.stopAllByTarget(this.startBtn);
                this.startBtn.setScale(new Vec3(1.0, 1.0, 1.0));
                this.menuLayer.getComponent(UIOpacity)!.opacity = 255;
                this.menuLayer.active = false;
            })
            .start();


        this.levelLayer.active = true;
        this.levelLayer.getComponent(UIOpacity)!.opacity = 0;

        tween(this.levelLayer)
            .delay(0.2)
            .call(()=>{
                this.updateLevelInfo();
            })
            .start();

        tween(this.levelLayer.getComponent(UIOpacity)! as any)
            .delay(0.1)
            .to(0.1, { opacity: 255 })
            .start();
    }

    levelBackCallBack (event: any, customEventData: any) {
        if(this.curLayer == 0){
           return;
        }
        this.playSound(sound.BUTTON);
        this.curLayer = 0;
        tween(this.levelLayer)
            .to(0.1, {}, {easing:'fade'})
            .call(()=>{
                this.levelLayer.getComponent(UIOpacity)!.opacity = 255;
                this.levelLayer.active = false;
            })
            .start();

        this.menuLayer.active = true;
        this.menuLayer.getComponent(UIOpacity)!.opacity = 0;

        tween(this.menuLayer.getComponent(UIOpacity)! as any)
            .to(0.1, { opacity: 255 })
            .start();

        this.menuLayerAni();
    }

    selectLevelCallBack (level: any) {
        this.curLevel = level;
        if(this.curLayer == 2){
           return;
        }
        this.playSound(sound.BUTTON);
        this.curLayer = 2;
        tween(this.levelLayer)
            .to(0.1, {}, {easing:'fade'})
            .call(()=>{
                this.levelLayer.getComponent(UIOpacity)!.opacity = 255;
                this.levelLayer.active = false;
            })
            .start();
            
        this.gameLayer.active = true;
        this.gameLayer.getComponent(UIOpacity)!.opacity = 0;
        tween(this.gameLayer)
            .delay(0.1)
            .call(()=>{
                this.createLevelLayer(level);
            })
            .start();

        tween(this.gameLayer.getComponent(UIOpacity)! as any)
            .delay(0.1)
            .to(0.1, { opacity: 255})
            .start();

    }

    backLevelCallBack (event?: any, customEventData?: any) {
        this.clearGameData();
        if(this.curLayer == 1){
           return;
        }
        this.playSound(sound.BUTTON);
        this.curLayer = 1;
        tween(this.gameLayer)
            .to(0.1, {}, {easing:'fade'})
            .call(()=>{
                this.gameLayer.getComponent(UIOpacity)!.opacity = 255;
                this.gameLayer.active = false;
            })
            .start();
            
        this.levelLayer.active = true;
        this.levelLayer.getComponent(UIOpacity)!.opacity = 0;
        tween(this.levelLayer.getComponent(UIOpacity)! as any)
            .to(0.1, {opacity: 255})
            .call(()=>{
                this.updateLevelInfo();
            })
            .start();

    }

    nextLevelCallBack (event: any, customEventData: any) {
        this.playSound(sound.BUTTON);
        this.gameOverLayer.active = false;
        this.curLevel = this.curLevel + 1;
        if(this.curLevel > this.allLevelCount){
           this.backLevelCallBack();
        }
        else{
           this.createLevelLayer(this.curLevel);
        }
    }

    refreshCallBack (event: any, customEventData: any) {
        this.playSound(sound.BUTTON);
        Tween.stopAllByTarget(this.gameLayer);
        this.clearGameData();
        this.createLevelLayer(this.curLevel);
    }

    revokeCallBack (event: any, customEventData: any) {
    }

    createLevelLayer (level: any) {
        this.gameControlLayer.removeAllChildren();
        this.setLevel();
        this.setCurNum();
        this.setBestNum();
        let levelContent = this.allLevelConfig[level].content;
        this.allRow = this.allLevelConfig[level].allRow;
        this.allCol = this.allLevelConfig[level].allCol;
        this.heroRow = this.allLevelConfig[level].heroRow;
        this.heroCol = this.allLevelConfig[level].heroCol;
        this.boxW = this.allWidth / this.allCol;
        this.boxH = this.boxW;
        let sPosX = -(this.allWidth / 2) + (this.boxW / 2);
        let sPosY = (this.allWidth / 2) - (this.boxW / 2);
        let offset = 0;
        if(this.allRow > this.allCol){
           offset = ((this.allRow - this.allCol) * this.boxH) / 2;
        }
        else{
           offset = ((this.allRow - this.allCol) * this.boxH) / 2;
        }
        this.landArrays = [];   //地图容器
        this.palace = [];       //初始化地图数据
        for(let i = 0; i < this.allRow; i++){
           this.landArrays[i] = [];  
           this.palace[i] = [];
        }
        for(let i = 0; i < this.allRow; i++){    //每行
           for(let j = 0; j < this.allCol; j++){     //每列
               let x = sPosX + (this.boxW * j);
               let y = sPosY - (this.boxH * i) + offset;
               let node = this.createBoxItem(i, j, levelContent[i * this.allCol + j], new Vec3(x, y, 0));
               this.landArrays[i][j] = node;
               node.getComponent(UITransform)!.setContentSize(new Size(this.boxW, this.boxH));
           }
        }
        this.setLandFrame(this.heroRow, this.heroCol, boxType.HERO);
    }

    createBoxItem (row: any, col: any, type: any, pos: any) {
        let node = new Node();
        node.layer = Layers.Enum.UI_2D;
        let sprite = node.addComponent(Sprite);
        let button = node.addComponent(Button);
        sprite.spriteFrame = this.itemImgAtlas.getSpriteFrame("p" + type);
        node.parent = this.gameControlLayer;
        node.position = pos;
        if(type == boxType.WALL){  //墙面，//墙面，命名为wall_row_col
           node.name = "wall_" + row + "_" + col;
           node.attr({"_type_" : type});
        }
        else if(type == boxType.NONE){  //空白区域,//墙面，命名为none_row_col
           node.name = "none_" + row + "_" + col;
           node.attr({"_type_" : type});
        }
        else{  //游戏界面，命名为land_row_col
           node.name = "land_" + row + "_" + col;
           node.attr({"_type_" : type});
           node.attr({"_row_" : row});
           node.attr({"_col_" : col});
           button.interactable = true;
           button.target = node;
           button.node.on('click', this.clickCallBack, this);
           if(type == boxType.ENDBOX){  //在目标点上的箱子，直接将完成的箱子数加1
               this.finishBoxCount += 1;
           }
        }
        this.palace[row][col] = type;
        return node;
    }

    clickCallBack (event: any, customEventData: any) {
        let target = event.target;
        this.minPath = this.allCol * this.allRow + 1;
        this.bestMap = [];
        this._end = {};
        this._end.row  = target._row_;
        this._end.col = target._col_;
        this._start = {};
        this._start.row = this.heroRow;
        this._start.col = this.heroCol;
        let endType = this.palace[this._end.row][this._end.col];
        if((endType == boxType.LAND) || (endType == boxType.BODY)){  //是空地或目标点，直接计算运动轨迹
           this.getPath(this._start, 0, []);
           if(this.minPath <= this.allCol * this.allRow){
               log("从起点[", this._start.row, ",", this._start.col, "]到终点[", 
               this._end.row, ",", this._end.col, "]最短路径长为：", this.minPath, "最短路径为：");
               log("[", this._start.row, ",", this._start.col, "]");
               for(let i = 0; i< this.bestMap.length;i++){
                   log("=>[",this.bestMap[i].row,",",this.bestMap[i].col,"]");
               }
               this.bestMap.unshift(this._start);
               this.runHero();
           }else{
               console.log("找不到路径到达");
           }
        }
        else if((endType == boxType.BOX) || (endType == boxType.ENDBOX)){ //是箱子，判断是否可以推动箱子
           let lr = this._end.row - this._start.row;
           let lc = this._end.col - this._start.col;
           if((Math.abs(lr) + Math.abs(lc)) == 1){  //箱子在人物的上下左右方位
               let nextr = this._end.row + lr;
               let nextc = this._end.col + lc;
               let t = this.palace[nextr][nextc];
               if(t && (t != boxType.WALL) && (t != boxType.BOX) && (t != boxType.ENDBOX)){  //前方不是障碍物，也不是墙壁，推动箱子
                   this.playSound(sound.PUSHBOX);
                   this.setLandFrame(this._start.row, this._start.col, this.palace[this._start.row][this._start.col]);
                   let bt = this.palace[this._end.row][this._end.col];
                   if(bt == boxType.ENDBOX){      //有目标物体的箱子类型，还原成目标点
                       this.palace[this._end.row][this._end.col] = boxType.BODY;
                       this.finishBoxCount -= 1;
                   }
                   else{
                       this.palace[this._end.row][this._end.col] = boxType.LAND;
                   }
                   this.setLandFrame(this._end.row, this._end.col, boxType.HERO);
                   let nt = this.palace[nextr][nextc];
                   if(nt == boxType.BODY){  //有目标点，将箱子类型设置成有目标箱子
                       this.palace[nextr][nextc] = boxType.ENDBOX;
                       this.finishBoxCount += 1;
                   }
                   else {
                       this.palace[nextr][nextc] = boxType.BOX;
                   }
                   this.setLandFrame(nextr, nextc, this.palace[nextr][nextc]);
                   this.curStepNum += 1;
                   this.setCurNum();
                   this.heroRow = this._end.row;
                   this.heroCol = this._end.col;
                   this.checkGameOver();
               }
               else{
                   this.playSound(sound.WRONG);
                   console.log("前方有障碍物");
               }
           }
           else{   //目标点错误
               this.playSound(sound.WRONG);
               console.log("目标点错误");
           }
        }
    }

    getPath (curPos: any, step: any, result: any) {
        if((curPos.row == this._end.row) && (curPos.col == this._end.col)){
           if(step < this.minPath){
               this.bestMap = [];
               for(let i = 0; i < result.length; i++){
                   this.bestMap.push(result[i]);
               }
               this.minPath = step; //如果当前抵达步数比最小值小，则修改最小值
               result = [];
           }
        }
        for(let i = (curPos.row - 1); i <= (curPos.row + 1); i++){
           for(let j = (curPos.col - 1); j <= (curPos.col + 1); j++){
               if((i < 0) || (i >= this.allRow) || (j < 0) || (j >= this.allCol)){
                   continue;
               }
               if((i != curPos.row) && (j != curPos.col)){//忽略斜角
                   continue;
               }
               else if(this.palace[i][j] && ((this.palace[i][j] == boxType.LAND) || (this.palace[i][j] == boxType.BODY))){
                   let tmp = this.palace[i][j];
                   this.palace[i][j] = boxType.WALL;  //标记为不可走
                   let r : any = {};
                   r.row = i;
                   r.col = j;
                   result.push(r);
                   this.getPath(r, step + 1, result);
                   this.palace[i][j] = tmp;  //尝试结束，取消标记
                   result.pop();
               }
           }
        }
    }

    runHero () {
        this.setLandEnable(false);

        let t = tween(this.gameLayer);
        for(let i = 1; i < this.bestMap.length; i++){
            t.delay(0.1);
            t.call(()=>{
                let pos = this.bestMap[i];
                this.setLandFrame(pos.row, pos.col, boxType.HERO);
                let lastPos = this.bestMap[i - 1];
                this.setLandFrame(lastPos.row, lastPos.col, this.palace[lastPos.row][lastPos.col]);
                this.curStepNum += 1;
                this.playSound(sound.MOVE);
                this.setCurNum();
            })
        }
        t.call(()=>{
            this.heroRow = this._end.row;
            this.heroCol = this._end.col;
            this.setLandEnable(true);
        })

        // @ts-ignore
        if(t['_actions'].length >= 2){  //避免出错
            t.start();
        }
        else{
           this.setLandEnable(true);
        }
    }

    setLandEnable (isEnable: any) {
        for(let i = 0; i < this.allRow; i++){
           for(let j = 0; j < this.allCol; j++){
               let land = this.landArrays[i][j];
               if(land){
                   land.getComponent(Button).interactable = isEnable;
               }
           }
        }
    }

    setLandFrame (row: any, col: any, type: any) {
        let land = this.landArrays[row][col] as Node;
        if(land){
            land.getComponent(Sprite)!.spriteFrame = this.itemImgAtlas.getSpriteFrame("p" + type);
            land.getComponent(UITransform)!.setContentSize(new Size(this.boxW, this.boxH));
        }
    }

    checkGameOver () {
        let count = this.allLevelConfig[this.curLevel].allBox;
        if(this.finishBoxCount == count){   
           this.gameOverLayer.active = true;
           this.gameOverLayer.getComponent(UIOpacity)!.opacity = 1;
           tween(this.gameOverLayer.getComponent(UIOpacity)! as any)
                .delay(0.5)
                .to(0.1, { opacity: 255 })
                .start();

           let finishLevel = parseInt(sys.localStorage.getItem("finishLevel") || 0);
           if(this.curLevel > finishLevel){
               sys.localStorage.setItem("finishLevel", this.curLevel);
           }
           sys.localStorage.setItem("levelStar" + this.curLevel, 3);
           let best = parseInt(sys.localStorage.getItem("levelBest" + this.curLevel) || 0);
           if((this.curStepNum < best) || (best == 0)){
               sys.localStorage.setItem("levelBest" + this.curLevel, this.curStepNum);
           }
           this.playSound(sound.GAMEWIN);
           this.clearGameData();
        }
    }

    setLevel () {
        this.levelTxt.getComponent(Label)!.string = this.curLevel.toString();
    }

    setCurNum () {
        this.curNum.getComponent(Label)!.string = this.curStepNum.toString();
    }

    setBestNum () {
        let bestNum = sys.localStorage.getItem("levelBest" + this.curLevel) || "--";
        this.bestNum.getComponent(Label)!.string = bestNum;
    }

    clearGameData () {
        this.finishBoxCount = 0;
        this.curStepNum = 0;
        this.curStepNum = 0;
    }

    playSound (name: string) {
        resources.load(name, AudioClip, (err: any, clip: any) => {
            if(err){
                console.log(err);
                return;
            }
            const tempNode = new Node();
            this.node.addChild(tempNode);
            let audio = tempNode.addComponent(AudioSource);
            audio.clip = clip;
            audio.play();
        });
    }

}
